﻿using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Wicture.DbRESTFul;
using Wicture.DbRESTFul.SQL;
using Wicture.EPC.Common;
using Wicture.EPC.Service.Etka.Models;
using Wicture.EPC.Service.Etka.Resources;

namespace Wicture.EPC.Service.Etka.QueryInterceptors.Interceptors
{
    public class ETKAQueryInterceptor : IQueryInterceptor
    {
        protected static char[] Spliter = ",".ToArray();
        protected virtual HashSet<string> IgnoreFamilys { get; } = new HashSet<string> { };

        public virtual string[] BrandCodes { get; } = new string[] { "vw", "audi", "skoda", "seat" };

        #region public method
        public virtual object QueryAllPartsByVin(DbRESTFulRepository repository, JObject param)
        {
            string token = param.Value<string>("token");
            if (token != "ffedfe5addbc4b36a1645270be5bbb8b")
            {
                throw new LogicalException($"未授权", 612);
            }
            string vinCode = param.Value<string>(ParamFields._vin);
            string brandCode = param.Value<string>(ParamFields._brandCode);
            string selectorId = param.Value<string>(ParamFields._id);
            List<Query_MainGroupResult> mainGroupList = repository.Invoke<Query_MainGroupResult>(CSIs.Query_Main_Group_For_ETKA, null);

            var vinInfo = GetVinInfo(repository, vinCode);
            var prCodes = vinInfo?.PRNumbers.Split(",").Distinct();
            List<PR> prInfoList = GetPRInfos(brandCode, prCodes, vinInfo);
            HashSet<string> vinComponents = new HashSet<string>();
            if (!string.IsNullOrEmpty(vinInfo.carportComponents))
            {
                vinComponents = new HashSet<string>(vinInfo.carportComponents.Split(',').Select(q => q.Replace(":", "1")));
            }
            List<VinPartResult> vinPartResults = new List<VinPartResult>();
            foreach (var mainGroup in mainGroupList)
            {
                JObject subGroupParam = new JObject();
                subGroupParam["mainGroupCode"] = mainGroup.code;
                subGroupParam["selectorId"] = selectorId;
                subGroupParam["brandCode"] = brandCode;
                var subGroupResult = GetSubGroupAndDesc(repository, subGroupParam);
                List<Query_SubGroupResult> subGroups = JArray.FromObject(subGroupResult.subGroups).ToObject<List<Query_SubGroupResult>>();
                var descriptionsMap = (subGroupResult.descriptions as IEnumerable<dynamic>).Select(i => new SubGroupDescription(i)).GroupBy(q => q.subGroupId).ToDictionary(k => k.Key, v => v.ToList());
                BuildEtkaSubGroup(subGroups, descriptionsMap, brandCode, prInfoList, vinInfo);
                subGroups = subGroups.Where(q => q.belongTo).ToList();
                foreach (var subGroup in subGroups)
                {
                    JObject partsParam = new JObject();
                    partsParam["selectorId"] = selectorId;
                    partsParam["brandCode"] = brandCode;
                    partsParam["id"] = subGroup.id;

                    List<Query_PartsResult> result = GetParts(repository, partsParam);

                    // 获取分组的描述信息（多个）
                    var subGroupDescriptions = ((IEnumerable<SubGroupDescription>)repository.Invoke<SubGroupDescription>(CSIs.Query_SubGroup_Description_For_ETKA, partsParam)).Where(d => d.uou == "U").ToList();
                    subGroupDescriptions.ForEach(x => x.isPart = false);

                    result = MergeSubGroupDescriptionsToParts(repository, brandCode, selectorId, subGroupDescriptions,
                result, new Func<VinFilter, bool>(f => VinFilterForPart(brandCode, prInfoList, vinInfo, f, vinComponents)));

                    MergeColorCodeParts(result, vinInfo);
                    result = result.Where(q => q.belongToVin && !string.IsNullOrEmpty(q.partCode)).ToList();
                    foreach (var part in result)
                    {
                        VinPartResult vpr = new VinPartResult()
                        {
                            vin = vinCode,
                            mainGroupCode = mainGroup.code,
                            subGroupCode = subGroup.code,
                            imageSN = subGroup.imageSN,
                            partCode = part.partCode,
                            partName = part.partName,
                            sn = part.imageSN,
                            subGroupId = subGroup.id
                        };
                        vinPartResults.Add(vpr);
                    }

                }
            }
            return vinPartResults;
        }
        /// <summary>
        /// 1、逐级选择车型
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object ModelSelector(DbRESTFulRepository repository, JObject param)
        {
            if (string.IsNullOrEmpty(param.Value<string>(ParamFields._id))) param[ParamFields._id] = "0";

            if (param.Value<string>(ParamFields._route) == ParamFields._mainGroup)
            {
                var mainGroupList = (List<Query_MainGroupResult>)QueryMainGroup(repository, param);
                return new Model_SelectorResult
                {
                    title = "选择主组",
                    items = mainGroupList
                        .Select(p => new Model_SelectorResult._items { id = p.id.ToString(), code = p.code, name = p.name, hasNext = false, route = "", imageUrl = p.imageUrl })
                        .ToList()
                };
            }
            else
            {
                List<ModelSelector> list = new List<ModelSelector>();
                var type = param.Value<string>(ParamFields._type);
                if (string.IsNullOrEmpty(type))
                {
                    list = (List<ModelSelector>)repository.Invoke<ModelSelector>(CSIs.List_Model_Selector, param);
                    //适应新的修改需求【主组先显示在车型选择中】：如果下一级是mainGroup，则给hasNext赋值为true,便于在车型选择中调用主组选择接口
                    list.Where(e => e.route == ParamFields._mainGroup).ForEach(e => e.hasNext = true);
                }
                else
                {
                    list = (List<ModelSelector>)repository.Invoke<ModelSelector>(CSIs.List_Model_Selector_App, param);
                }
                if (list.Count() < 1) throw new LogicalException($"参数错误", "612");
                if (list.First().name == "选择年款")
                {
                    list = list.OrderByDescending(q => q.objectName).ToList();
                }
                return new Model_SelectorResult
                {
                    title = list[0].name,
                    items = list
                   .Select(p => new Model_SelectorResult._items { id = p.id.ToString(), code = p.objectCode, name = p.objectName, description = p.description, hasNext = p.hasNext, route = p.route })
                   .ToList()
                };
            }
        }

        /// <summary>
        /// 2、查询主组
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QueryMainGroup(DbRESTFulRepository repository, JObject param)
        {
            string currentMainGroupId = "-1";
            List<Query_MainGroupResult> list = null;
            var id = param.Value<string>(ParamFields._id);
            var selectorId = param.Value<string>(ParamFields._selectorId);
            var brandCode = param.Value<string>(ParamFields._brandCode);
            if (string.IsNullOrEmpty(id))
            {
                param[ParamFields._id] = id = "0";
            }
            var vin = param.Value<string>(ParamFields._vin);

            if (string.IsNullOrEmpty(vin) && !string.IsNullOrEmpty(selectorId))
            {
                currentMainGroupId = id;
            }

            list = repository.Invoke<Query_MainGroupResult>(CSIs.Query_Main_Group_For_ETKA, new { brandCode });

            var item = list.FirstOrDefault(e => e.id == currentMainGroupId);
            if (item != null) item.isSelected = true;

            return list;
        }

        /// <summary>
        /// 3、查询子组
        /// </summary>  
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QuerySubGroup(DbRESTFulRepository repository, JObject param)
        {
            param.RemoveEmptyValues();

            var brandCode = param.Value<string>(ParamFields._brandCode);

            var id = param.Value<string>(ParamFields._id);
            param["mainGroupCode"] = id;
            var vin = param.Value<string>(ParamFields._vin);
            var result = GetSubGroupAndDesc(repository, param);
            List<Query_SubGroupResult> subGroups = JArray.FromObject(result.subGroups).ToObject<List<Query_SubGroupResult>>();
            var descriptionsMap = (result.descriptions as IEnumerable<dynamic>).Select(i => new SubGroupDescription(i)).GroupBy(q => q.subGroupId).ToDictionary(k => k.Key, v => v.ToList());

            return BuildEtkaSubGroup(subGroups, descriptionsMap, vin, brandCode, repository, null);
        }

        /// <summary>
        /// 6.查询热点信息
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QueryHotPoints(DbRESTFulRepository repository, JObject param)
        {
            IEnumerable<HotPoint> data = repository.Invoke<HotPoint>(CSIs.Query_HotPoints_For_ETKA, param);
            if (data.Count() == 0) return null;
            var hotPointGroup = data.GroupBy(e => e.url);

            var imgs = new List<Query_HotPointsResult._imgs>();
            foreach (var item in hotPointGroup)
            {
                imgs.Add(new Query_HotPointsResult._imgs
                {
                    url = item.Key,
                    points = (from n in item
                              select new Query_HotPointsResult._imgs._points
                              {
                                  sn = n.sn,
                                  x = n.x,
                                  y = n.y,
                                  w = n.w,
                                  h = n.h
                              }).ToList()

                });
            }
            return new { imgs };
        }

        /// <summary>
        /// 7.指定车型的详细信息
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QueryModel(DbRESTFulRepository repository, JObject param)
        {
            Get_Model_InfoParam request = param.ToObject<Get_Model_InfoParam>();
            Get_Model_InfoResult result = new Get_Model_InfoResult();
            string featureCodes = null;
            int? productionDate = null;
            string episType = "";
            string referenceCodes = "";
            string brandCode = "";
            if (!string.IsNullOrEmpty(request.vin))
            {
                IEnumerable<dynamic> vins = GetVinDetails(repository, request);
                if (!vins.Any()) throw new LogicalException($"ETKA数据库无法查询到Vin:{request.vin}，将使用代理服务器查询。", 610);
                var vin = GetSingleVin(vins);
                if (vin == null || vin.selectorId == null) return null;
                productionDate = ((string)vin.ProductionDate)?.Replace("-", "").ToInt();
                episType = vin.episType?.ToString();
                featureCodes = vin.PRNumbers;
                result = JObject.FromObject(vin).ToObject<Get_Model_InfoResult>();
                string finish = vin.ColorFinish;
                string paint = vin.Paint;
                string colorAndPaint = $"{finish}/{paint}".Trim('/');
                result.description = $"{vin.NAME}>{vin.ModelYear}>{vin.Transmission}";
                //车型，车系，年款，版本，车架号，品牌，品牌代码，变速箱，车身颜色，发动机，发动机类型，排量，生产日期
                var props = new List<Get_Model_InfoResult._props>
                 {
                        new Get_Model_InfoResult._props{ key="车型",value=vin.NAME},
                        new Get_Model_InfoResult._props{ key="年款",value=vin.ModelYear??""},//重要属性
                        new Get_Model_InfoResult._props{ key="生产日期",value=vin.ProductionDate},
                        new Get_Model_InfoResult._props{ key="销售类型",value=vin.SalesType??""},
                        new Get_Model_InfoResult._props{ key="车架号",value=vin.vin??""},
                        new Get_Model_InfoResult._props{ key="配置",value=vin.ColorInterior??""},
                        new Get_Model_InfoResult._props{ key="顶部颜色",value=vin.Color_Top??""},
                        new Get_Model_InfoResult._props{ key="车身颜色",value=colorAndPaint??""},
                        new Get_Model_InfoResult._props{ key="变速箱",value=vin.TransmissionCode??""},//重要属性
                        new Get_Model_InfoResult._props{ key="变速箱描述",value=vin.Transmission??""},
                        new Get_Model_InfoResult._props{ key="发动机",value=vin.EngineCode??""}//重要属性
                 };
                referenceCodes = vin.ReferenceCodes;
                brandCode = vin.brandCode;

                result.props = props.Where(q => !string.IsNullOrEmpty(q.value)).ToList();
            }
            else
            {
                var model = repository.Invoke(CSIs.Get_Model, param);
                if (model != null)
                {
                    result = JObject.FromObject(model).ToObject<Get_Model_InfoResult>();
                    result.props = GetModelProps(repository, param);
                }
            }

            result.features = new List<Get_Model_InfoResult._features>();
            if (!string.IsNullOrEmpty(featureCodes))
            {
                var featureCodeList = featureCodes.Split(",");
                IEnumerable<Feature> features = repository.Invoke<Feature>(CSIs.Query_Vin_Features_For_ETKA, new { featureCodes = featureCodeList });
                foreach (var feature in features.GroupBy(q => q.featureCode))
                {
                    var orderedList = feature.OrderByDescending(q => q.modelEpisTypes).ThenByDescending(q => q.startDate).ThenByDescending(q => q.endDate);
                    foreach (var info in orderedList)
                    {
                        if (productionDate.HasValue && info.startDate.HasValue && productionDate.Value < info.startDate)
                        {
                            continue;
                        }
                        if (productionDate.HasValue && info.endDate.HasValue && productionDate.Value > info.endDate)
                        {
                            continue;
                        }
                        if (!string.IsNullOrEmpty(episType) && !string.IsNullOrEmpty(info.modelEpisTypes) && !info.modelEpisTypes.Split(',').Contains(episType))
                        {
                            continue;
                        }
                        result.features.Add(new Get_Model_InfoResult._features() { key = info.featureCode, value = info.featureName });
                        if (info.featureFamily == "ATA")
                        {
                            result.props.Add(new Get_Model_InfoResult._props { key = "驱动方式标识字母", value = info.featureCode });//重要属性
                        }

                        if (info.featureFamily == "MOT" && !string.IsNullOrEmpty(info.featureName))
                        {
                            var engineInfo = "";
                            var engineInfos = info.featureName.Split("$$");

                            if (engineInfos.Length >= 3)
                            {
                                engineInfo = $"{engineInfos[0]} {engineInfos[1]}";
                            }
                            else
                            {
                                engineInfo = engineInfos[0];
                            }
                            result.props.Add(new Get_Model_InfoResult._props { key = "发动机描述", value = engineInfo });//重要属性
                            result.description += $">{engineInfo}";
                        }
                        break;
                    }
                }
                var addFeatures = featureCodeList.Except(result.features.Select(q => q.key));
                foreach (var addFeature in addFeatures)
                {
                    result.features.Add(new Get_Model_InfoResult._features { key = addFeature, value = "" });
                }
            }
            if (!string.IsNullOrEmpty(referenceCodes))
            {
                var referenceCodeArray = referenceCodes.Split(',').Where(q => !string.IsNullOrEmpty(q));
                if (referenceCodeArray.Any())
                {
                    var refernceText = ResourceHelper.Textify(brandCode, referenceCodeArray, "; ");
                    if (!string.IsNullOrEmpty(refernceText))
                    {
                        result.props.Add(new Get_Model_InfoResult._props { key = "注意", value = refernceText.Replace("$$", "") });
                    }
                }

            }
            return result;
        }
        /// <summary>
        /// 获取面包屑导航栏
        /// </summary>
        /// <param name="repository"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object GetCrumbs(DbRESTFulRepository repository, JObject param)
        {
            var brandCode = param.Value<string>(ParamFields._brandCode);
            List<Query_CrumbResult> result = new List<Query_CrumbResult>();
            var id = param.Value<string>(ParamFields._id);
            var selectorId = param.Value<string>(ParamFields._selectorId);

            var vinCode = param.Value<string>(ParamFields._vin);
            if (!string.IsNullOrEmpty(id))
            {
                var route = param.Value<string>(ParamFields._route);
                var ids = id.Split(SplitFields.UnderLine);
                if (route == ParamFields._subGroup)
                {
                    var subGroupId = ids[0];
                    SubGroupInfo subGroupInfo = repository.Invoke<SubGroupInfo>(CSIs.Get_SubGroup_By_Id, new { brandCode, subGroupId });
                    subGroupInfo.subGroupName = GetSubGroupName(brandCode, subGroupInfo.subGroupDescCodes);
                    if (string.IsNullOrEmpty(vinCode))
                    {
                        result = repository.Invoke<Query_CrumbResult>(CSIs.Get_Crumbs, new { brandCode, id = selectorId });
                    }
                    result.Add(new Query_CrumbResult { id = subGroupInfo.mainGroupCode, code = subGroupInfo.mainGroupCode, name = subGroupInfo.mainGroupName, fieldCode = ParamFields._mainGroup, fieldName = "主组", step = 100, selectorId = selectorId });
                    result.Add(new Query_CrumbResult { id = id, code = subGroupInfo.subGroupCode, name = subGroupInfo.subGroupName, fieldCode = ParamFields._subGroup, fieldName = "分组", step = 101, selectorId = selectorId });
                }
                else if (route == ParamFields._mainGroup)
                {
                    var mainGroupCode = ids[0];
                    SubGroupInfo mainGroupInfo = repository.Invoke<SubGroupInfo>(CSIs.Get_MainGroup_By_Id, new { mainGroupCode });
                    if (string.IsNullOrEmpty(vinCode))
                    {
                        result = repository.Invoke<Query_CrumbResult>(CSIs.Get_Crumbs, new { brandCode, id = selectorId });
                    }
                    result.Add(new Query_CrumbResult { id = mainGroupInfo.mainGroupCode, code = mainGroupInfo.mainGroupCode, name = mainGroupInfo.mainGroupName, fieldCode = ParamFields._mainGroup, fieldName = "主组", step = 100, selectorId = selectorId });
                }
                else if (route == "catalog")
                {
                    if (string.IsNullOrEmpty(vinCode))
                    {
                        result = repository.Invoke<Query_CrumbResult>(CSIs.Get_Crumbs, new { brandCode, id });
                        result.Last().selectorId = id;
                    }
                }
                else
                {
                    result = repository.Invoke<Query_CrumbResult>(CSIs.Get_Crumbs, param);
                }
            }
            if (string.IsNullOrEmpty(vinCode))
            {
                result.Insert(0, new Query_CrumbResult { code = brandCode, name = BrandDictionary.GetName(brandCode), step = 0, fieldCode = ParamFields._brandCode, fieldName = "选择品牌" });
            }
            return result;
        }

        /// <summary>
        /// EPC内零件搜索
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object SearchParts(DbRESTFulRepository repository, JObject param)
        {
            param.RemoveEmptyValues();
            var selectorId = param.Value<string>(ParamFields._selectorId);
            var partCodes = param.Value<string>("partCodes");
            var brandCode = param.Value<string>(ParamFields._brandCode);
            var vin = param.Value<string>(ParamFields._vin);
            List<SearchSubGroupResults> searchResults = repository.Invoke<SearchSubGroupResults>(CSIs.Search_SubGroups_By_PartCodes, new { brandCode, partCodes = partCodes.Split(','), selectorId });
            List<Query_MainGroupResult> mainGroups = searchResults.Select(q => new { q.mainGroupCode, q.mainGroupName }).Distinct().Select(x => new Query_MainGroupResult { id = x.mainGroupCode, code = x.mainGroupCode, name = x.mainGroupName, isBelongTo = true }).ToList();
            List<Query_SubGroupResult> subGroups = searchResults.GroupBy(q => q.subGroupId).Select(x => new Query_SubGroupResult { code = x.First().subGroupCode, belongTo = true, imageSN = x.First().imageSN, imageUrl = x.First().imageUrl, mainGroupId = x.First().mainGroupCode, mainGroupCode = x.First().mainGroupCode, name = x.First().subGroupName, id = x.Key, subGroupDescCodes = x.First().subGroupDescCodes }).ToList();
            Dictionary<string, List<SubGroupDescription>> descriptionsMap = ((List<SubGroupDescription>)repository.Invoke<SubGroupDescription>(CSIs.Query_SubGroup_Description_By_Ids, new { subGroupIds = subGroups.Select(q => q.id) })).GroupBy(q => q.subGroupId).ToDictionary(k => k.Key, v => v.ToList());
            BuildEtkaSubGroup(subGroups, descriptionsMap, vin, brandCode, repository, null);
            return new SearchPartsResult { mainGroups = mainGroups, subGroups = subGroups };
        }
        /// <summary>
        /// 获取适用车型信息
        /// </summary>
        /// <param name="repository"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QueryFitModels(DbRESTFulRepository repository, JObject param)
        {
            var pageIndex = param.Value<int>(ParamFields._pageIndex);
            var pageSize = 20;
            var brandCode = param.Value<string>(ParamFields._brandCode);
            var partCode = param.Value<string>(ParamFields._partCode);
            var pageStart = pageIndex * pageSize + 1;
            var model = repository.Invoke(CSIs.Query_Models_By_Part, new { pageIndex, pageStart, pageSize, brandCode, partCode });
            Query_FitModelsResult result = JObject.FromObject(model).ToObject<Query_FitModelsResult>();
            if (result == null || result.items == null || !result.items.Any())
            {
                return result;
            }
            var hotPointsModel = repository.Invoke(CSIs.Query_HotPoints_By_Part, new { brandCode, partIds = result.items.Select(q => q.partId).Distinct() });
            JArray allPoints = JArray.FromObject(hotPointsModel);

            foreach (var item in result.items)
            {
                //2.1适用车型多张图片处理
                var imgs = allPoints.Where(p => p.Value<string>("partId") == item.partId).Select(p => p.ToObject<Query_FitModelsResult._models._imgs>()).GroupBy(q => new { q.partId, q.url }).Select(q => new Query_FitModelsResult._models._imgs { partId = q.Key.partId, url = q.Key.url }).ToList();
                foreach (var img in imgs)
                {
                    //2.1.1适用车型每张图片对应的热点处理
                    img.points = allPoints.Where(p => p.Value<string>("url") == img.url && p.Value<string>("partId") == img.partId)
                                       .Select(p => p.ToObject<Query_FitModelsResult._models._imgs._points>()).ToList();
                }
                item.imgs = imgs.Where(p => p.points.Count > 0).ToList();
                item.imageUrl = item.imgs.FirstOrDefault()?.url;
                item.subGroupName = GetSubGroupName(brandCode, item.subGroupDescCodes);
            }
            return result;
        }

        /// <summary>
        /// 4、查询零件列表（通过子组或vin码）
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QueryParts(DbRESTFulRepository repository, JObject param)
        {
            param.RemoveEmptyValues();
            var idParams = param.Value<string>(ParamFields._id).Split(SplitFields.UnderLine);
            if (idParams.Length > 1) param[ParamFields._id] = idParams[0];
            string brandCode = param.Value<string>(ParamFields._brandCode);
            string vin = param.Value<string>(ParamFields._vin);
            string selectorId = param.Value<string>(ParamFields._selectorId);

            List<Query_PartsResult> result = GetParts(repository, param);

            // 获取分组的描述信息（多个）
            var subGroupDescriptions = ((IEnumerable<SubGroupDescription>)repository.Invoke<SubGroupDescription>(CSIs.Query_SubGroup_Description_For_ETKA, param)).Where(d => d.uou == "U").ToList();
            subGroupDescriptions.ForEach(x => x.isPart = false);

            // 如果参数中没有vin码，则将 belongToVin 设置为 true。
            if (string.IsNullOrEmpty(vin))
            {
                return MergeSubGroupDescriptionsToParts(repository, brandCode, selectorId, subGroupDescriptions, result, null);
            }

            // 如果分组不属于此VIN，只直接返回（belongToVin 默认都为 false）
            //var subGroupBelongToVin = true;
            //if (idParams.Length == 2 && idParams[1] == "0")
            //{
            //    subGroupBelongToVin = false;
            //}
            //if (!subGroupBelongToVin)
            //{
            //    return MergeSubGroupDescriptionsToParts(repository, brandCode, selectorId, subGroupDescriptions, result, a => false);
            //};


            VinInfo vinInfo = GetVinInfo(repository, vin);
            if (vinInfo == null) return result;
            HashSet<string> vinComponents = new HashSet<string>();
            if (!string.IsNullOrEmpty(vinInfo.carportComponents))
            {
                vinComponents = new HashSet<string>(vinInfo.carportComponents.Split(',').Select(q => q.Replace(":", "1")));
            }
            var prCodes = vinInfo?.PRNumbers.Split(",").Distinct();

            List<PR> prInfoList = GetPRInfos(brandCode, prCodes, vinInfo);

            //result = result.Where(q => q.partCode == "99134780315").ToList();
            result = MergeSubGroupDescriptionsToParts(repository, brandCode, selectorId, subGroupDescriptions,
                result, new Func<VinFilter, bool>(f => VinFilterForPart(brandCode, prInfoList, vinInfo, f, vinComponents)));

            result =  MergeColorCodeParts(result, vinInfo);
            return result;
        }

        /// <summary>
        /// 5、获取配件详情
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QueryPartDetail(DbRESTFulRepository repository, JObject param)
        {
            var brandCode = param.Value<string>(ParamFields._brandCode);
            var partId = param.Value<string>(ParamFields._partId);
            var marketCode = param.Value<string>("marketCode");
            if (marketCode == null)
            {
                param["marketCode"] = "";
            }
            Query_Part_DetailResult result = null;
            if (string.IsNullOrEmpty(partId))
            {

                result = repository.Invoke<Query_Part_DetailResult>(CSIs.Get_Part_Detail, param);
            }
            else
            {
                result = repository.Invoke<Query_Part_DetailResult>(CSIs.Get_Usage_Detail, param);
            }

            if (result == null) return null;


            List<Supersession> supersessions = repository.Invoke<Supersession>(CSIs.Get_Superseesion_For_Etka, new { result.partCode });
            List<Supersession> interchanges = repository.Invoke<Supersession>(CSIs.Get_Part_Interchange, new { result.partCode });

            if (!string.IsNullOrEmpty(partId))
            {
                IEnumerable<Part_Price> partAttrs = repository.Invoke<Part_Price>(CSIs.Get_Part_Price, new { result.partCode });
                partAttrs = partAttrs.Where(q => !q.marketCode.Equals(result.marketCode) && !q.brandCode.Equals(brandCode));
                if (partAttrs.Any())
                {
                    result.partAttributes = partAttrs.Select(q => new Query_Part_DetailResult._partAttribute
                    {
                        partCode = q.partCode,
                        marketCode = q.marketCode,
                        marketName = q.marketName,
                        brandCode = q.brandCode,
                        brandName = BrandDictionary.GetName(q.brandCode),
                        cost = q.cost,
                        price = q.price,
                        stock = q.stock,
                        priceUpdatedAt = q.priceUpdatedAt,
                        priceFC = q.priceFC,
                        props = new List<Query_Part_DetailResult._prop> { new Query_Part_DetailResult._prop { key = "厂商", value = q.marketName } }
                    }).ToList();
                }
            }
            //3.替换件处理
            if (supersessions != null && supersessions.Count > 0)
            {
                result.replacements = new List<Query_Part_DetailResult._replacement>();
                foreach (var q in supersessions)
                {
                    var replacement = new Query_Part_DetailResult._replacement { brandCode = q.brandCode ?? brandCode, brandName = BrandDictionary.GetName(q.brandCode ?? brandCode), cost = q.cost, partCode = q.partCode, partName = string.IsNullOrEmpty(q.partName) ? "暂无" : q.partName, price = q.price, stock = q.stock, priceFC = q.priceFC, priceUpdatedAt = q.priceUpdatedAt, marketCode = q.marketCode, marketName = q.marketName };
                    replacement.props = new List<Query_Part_DetailResult._prop>();
                    if (!string.IsNullOrEmpty(q.marketName))
                    {
                        replacement.props.Add(new Query_Part_DetailResult._prop { key = "厂商", value = q.marketName });
                    }
                    if (q.disabled.HasValue)
                    {
                        replacement.props.Add(new Query_Part_DetailResult._prop { key = "是否禁用", value = q.disabled.HasValue ? (q.disabled.Value ? "是" : "否") : "" });
                    }
                    result.replacements.Add(replacement);
                }
                result.replacements = result.replacements.OrderByDescending(q => q.partCode).ThenByDescending(q => q.brandCode == brandCode).ToList();
            }

            string modelNotes = result.modelFilter;
            string notes = "";
            if (!string.IsNullOrEmpty(result.TSBem))
            {
                notes = ResourceHelper.Textify(brandCode, result.TSBem.Split(Spliter, StringSplitOptions.RemoveEmptyEntries).Distinct());
            }
            if (!string.IsNullOrEmpty(result.description))
            {
                notes += "\r\n" + result.description;
            }
            if (!string.IsNullOrEmpty(notes))
            {
                notes = notes.Trim().Trim(',');
            }

            //4.配件附加属性处理
            var props = new List<Query_Part_DetailResult._prop>
            {
                new Query_Part_DetailResult._prop { key = "件数", value = result.quantity ?? string.Empty },
                new Query_Part_DetailResult._prop { key = "型号", value = modelNotes ?? string.Empty },
                new Query_Part_DetailResult._prop { key = "备注", value = notes ?? string.Empty },
                new Query_Part_DetailResult._prop { key = "是否禁用", value = result.disabled.HasValue? (result.disabled.Value ? "是":"否"): ""  }
            };
            if (interchanges != null && interchanges.Count > 0)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var interchange in interchanges)
                {
                    sb.Append(interchange.partCode);
                    if (!string.IsNullOrEmpty(interchange.brandCode))
                    {
                        sb.Append($"({BrandDictionary.GetName(interchange.brandCode)})");
                    }
                    sb.Append(",");
                }
                props.Add(new Query_Part_DetailResult._prop { key = "互换件", value = sb.ToString().Trim(',') });
            }
            if (marketCode == "SVW")
            {
                props.Add(new Query_Part_DetailResult._prop { key = "注意", value = "上汽大众各地销价不统一, 我站统一采取30%加价率" });
            }
            result.props = props.Where(q => !string.IsNullOrEmpty(q.value)).ToList();
            return result;
        }
        /// <summary>
        /// 根据vin过滤零件
        /// </summary>
        /// <param name="repository"></param>
        /// <param name="param"></param>
        /// <returns>零件的详情包括主组分组图等信息</returns>
        public object QueryPartsFilterByVin(DbRESTFulRepository repository, JObject param)
        {
            string vinCode = param.Value<string>(ParamFields._vin);
            string brandCode = param.Value<string>(ParamFields._brandCode);

            IEnumerable<dynamic> vins = GetVinDetails(repository, new Get_Model_InfoParam { brandCode = brandCode, vin = vinCode });
            if (!vins.Any()) throw new LogicalException($"找不到vin", 610);
            var vin = GetSingleVin(vins);
            if (vin == null || vin.selectorId == null) throw new LogicalException($"找不到vin", 610);

            string[] partCodes = param.Value<string>(ParamFields._partCodes)?.Split(',');
            return GetPartsFilterByVin(repository, partCodes, vin, brandCode, vinCode);
        }

        public object QueryMaintainParts(DbRESTFulRepository repository, JObject param)
        {
            string vinCode = param.Value<string>(ParamFields._vin);
            string brandCode = param.Value<string>(ParamFields._brandCode);

            IEnumerable<dynamic> vins = GetVinDetails(repository, new Get_Model_InfoParam { brandCode = brandCode, vin = vinCode });
            if (!vins.Any()) throw new LogicalException($"找不到vin", 612);
            var vin = GetSingleVin(vins);
            if (vin == null || vin.selectorId == null) throw new LogicalException($"找不到vin", 610);
            int modelId = vin.modelId;
            List<MaintainPart> maintainParts = repository.Invoke<MaintainPart>(CSIs.Query_Maintain_Parts, new { modelId });
            if (!maintainParts.Any())
            {
                return null;
            }
            List<PartCatalog> PartCatalog = GetPartsFilterByVin(repository, maintainParts.Select(q => q.partCode).Distinct(), vin, brandCode, vinCode);

            PartCatalog = (from p in PartCatalog
                           join m in maintainParts on p.partCode equals m.partCode
                           select MergePartCatalog(p, m)).ToList();
            return PartCatalog;
        }

        private PartCatalog MergePartCatalog(PartCatalog partCatalog, MaintainPart maintainPart)
        {
            partCatalog.standardCode = maintainPart.standardCode;
            partCatalog.standardName = maintainPart.standardName;
            return partCatalog;
        }

        private List<PartCatalog> GetPartsFilterByVin(DbRESTFulRepository repository, IEnumerable<string> partCodes, dynamic vin, string brandCode, string vinCode)
        {
            int selectorId = vin.selectorId;
            VinInfo vinInfo = JObject.FromObject(vin).ToObject<VinInfo>();
            List<PartCatalogResult> partCatalogResults = repository.Invoke<PartCatalogResult>(CSIs.Query_Parts_Filter_By_Model, new { selectorId, partCodes });

            var dicSubgroupParts = partCatalogResults.GroupBy(q => q.subGroupId).ToDictionary(k => k.Key, v => v.ToList());

            var result = GetSubGroupAndDescByIds(repository, new { subGroupIds = dicSubgroupParts.Keys.ToArray() });

            List<Query_SubGroupResult> subGroups = JArray.FromObject(result.subGroups).ToObject<List<Query_SubGroupResult>>();
            var descriptionsMap = (result.descriptions as IEnumerable<dynamic>).Select(i => new SubGroupDescription(i)).GroupBy(q => q.subGroupId).ToDictionary(k => k.Key, v => v.ToList());

            subGroups = BuildEtkaSubGroup(subGroups, descriptionsMap, vinCode, brandCode, repository, vinInfo);

            var prCodes = vinInfo?.PRNumbers.Split(",").Distinct();
            List<PR> prInfoList = GetPRInfos(brandCode, prCodes, vinInfo);
            HashSet<string> vinComponents = new HashSet<string>();
            if (!string.IsNullOrEmpty(vinInfo.carportComponents))
            {
                vinComponents = new HashSet<string>(vinInfo.carportComponents.Split(',').Select(q => q.Replace(":", "1")));
            }
            List<PartCatalogResult> filterdResults = new List<PartCatalogResult>();
            foreach (var subGroup in subGroups)
            {
                if (!subGroup.belongTo)
                {
                    continue;
                }
                var parts = dicSubgroupParts[subGroup.id.ToInt()];
                MergeSubGroupDescriptionsToParts2(descriptionsMap[subGroup.id], parts, new Func<VinFilter, bool>(f => VinFilterForPart(brandCode, prInfoList, vinInfo, f, vinComponents)));
                foreach (var part in parts)
                {
                    if (part.belongToVin)
                    {
                        part.subGroupName = subGroup.name;
                        part.imageSN = subGroup.imageSN;
                        filterdResults.Add(part);
                    }
                }
            }

            if (filterdResults.Any())
            {
                filterdResults = filterdResults.GroupBy(q => new { q.partCode, q.subGroupId, q.sn }).Select(p => new PartCatalogResult
                {
                    partCode = p.Key.partCode,
                    subGroupId = p.Key.subGroupId,
                    sn = p.Key.sn,
                    cost = p.First().cost,
                    price = p.First().price,
                    stock = p.First().stock,
                    id = p.First().id,
                    mainGroupId = p.First().mainGroupId,
                    mainGroupCode = p.First().mainGroupCode,
                    mainGroupName = p.First().mainGroupName,
                    subGroupCode = p.First().subGroupCode,
                    subGroupName = p.First().subGroupName,
                    imageSN = p.First().imageSN,
                    selectorId = p.First().selectorId,
                    partName = p.First().partName,
                    priceUpdatedAt = p.First().priceUpdatedAt,
                    priceFC = p.First().priceFC,
                    replacedPartCodes = p.First().replacedPartCodes,
                    marketCode = p.First().marketCode,
                    marketName = p.First().marketName,
                    disabled = p.First().disabled
                }).ToList();
                var hotPointsModel = repository.Invoke(CSIs.Query_HotPoints_By_Part, new { brandCode, partIds = filterdResults.Select(q => q.id).Distinct() });
                JArray allPoints = JArray.FromObject(hotPointsModel);

                foreach (var item in filterdResults)
                {
                    //2.1适用车型多张图片处理
                    var imgs = allPoints.Where(p => p.Value<string>("partId") == item.id).Select(p => p.Value<string>("url")).Distinct().Select(x => new _imgs { url = x }).ToList();
                    foreach (var img in imgs)
                    {
                        //2.1.1适用车型每张图片对应的热点处理
                        img.points = allPoints.Where(p => p.Value<string>("url") == img.url && p.Value<string>("partId") == item.id)
                                           .Select(p => p.ToObject<_imgs._points>()).ToList();
                    }
                    item.imgs = imgs;
                }
            }
            return BuildPartCatalogList(filterdResults);
        }

        private List<PartCatalog> BuildPartCatalogList(List<PartCatalogResult> partCatalogResults)
        {
            List<PartCatalog> retList = new List<PartCatalog>();
            var dic = partCatalogResults.GroupBy(x => x.partCode).ToDictionary(k => k.Key, v => v.ToList());
            foreach (var partCode in dic.Keys)
            {

                var list = dic[partCode];
                var first = list.First();
                PartCatalog info = new PartCatalog()
                {
                    partCode = partCode,
                    marketCode = first.marketCode,
                    marketName = first.marketName,
                    partName = first.partName,
                    price = first.price,
                    cost = first.cost,
                    disabled = first.disabled,
                    priceFC = first.priceFC,
                    priceUpdatedAt = first.priceUpdatedAt,
                    replacedPartCodes = first.replacedPartCodes
                };

                info.groups = list.Select(q => new PartCatalog._group
                {
                    partId = q.id,
                    imageSN = q.imageSN,
                    mainGroupCode = q.mainGroupCode,
                    mainGroupId = q.mainGroupId,
                    mainGroupName = q.mainGroupName,
                    selectorId = q.selectorId?.ToString(),
                    subGroupCode = q.subGroupCode,
                    subGroupId = q.subGroupId.ToString(),
                    subGroupName = q.subGroupName,
                    imgs = q.imgs,
                    sn = q.sn
                }).ToList();
                retList.Add(info);
            }

            return retList;
        }

        private dynamic GetSubGroupAndDescByIds(DbRESTFulRepository repository, object param)
        {
            return repository.Invoke(CSIs.Query_SubGroup_And_Desc_By_Ids, param);
        }
        #endregion

        #region protected method
        protected virtual List<Get_Model_InfoResult._props> GetModelProps(DbRESTFulRepository repository, JObject param)
        {
            var ret = new List<Get_Model_InfoResult._props>();
            List<Query_CrumbResult> list = repository.Invoke<Query_CrumbResult>(CSIs.Get_Crumbs_App, param);
            foreach (var info in list)
            {
                if (string.IsNullOrEmpty(info.id))
                {
                    continue;
                }
                if (info.fieldCode == "model")
                {
                    ret.Add(new Get_Model_InfoResult._props { key = "厂商", value = info.name.Split('@')[0] });
                    ret.Add(new Get_Model_InfoResult._props { key = "车型", value = info.name.Split('@')[1] });
                }
                else
                {
                    ret.Add(new Get_Model_InfoResult._props { key = info.fieldName?.Replace("选择", ""), value = info.name });
                }
            }
            return ret;
        }
        protected virtual dynamic GetSubGroupAndDesc(DbRESTFulRepository repository, JObject param)
        {
            return repository.Invoke(CSIs.Query_SubGroup_And_Desc_For_ETKA, param);
        }
        protected virtual VinInfo GetVinInfo(DbRESTFulRepository repository, string vin)
        {
            List<VinInfo> list = repository.Invoke<VinInfo>(CSIs.Query_vin_for_etka_step2, new { vin });
            if (list == null || list.Count == 0) return null;
            if (list.Count == 1) return list.First();
            foreach (var info in list)
            {
                int? productionDate = info.productionDate?.Replace("-", "").Substring(0, 6).ToInt();
                if (!productionDate.HasValue)
                {
                    return info;
                }
                int? startDate = info.modelYearStart;
                if (startDate.HasValue && startDate.Value > productionDate.Value)
                {
                    continue;
                }
                int? endDate = info.modelYearEnd;
                if (endDate.HasValue && endDate.Value < productionDate.Value)
                {
                    continue;
                }
                return info;
            }
            return list.First();
        }

        protected List<Query_SubGroupResult> BuildEtkaSubGroup(List<Query_SubGroupResult> subGroups, Dictionary<string, List<SubGroupDescription>> descriptionsMap, string vin, string brandCode, DbRESTFulRepository repository, VinInfo vinInfo)
        {
            foreach (var subGroup in subGroups)
            {
                // 有多个分组描述，合并相关的值，再处理。
                var items = descriptionsMap.Keys.Contains(subGroup.id) ? descriptionsMap[subGroup.id] : new List<SubGroupDescription>();

                // 根据描述信息，更新名称等信息。
                SetSubGroupText(brandCode, subGroup, items);
            }

            // 不包含VIN码时，直接返回。
            if (string.IsNullOrEmpty(vin))
            {
                return subGroups;
            }
            // 处理通过VIN码过滤分组
            else
            {
                if (vinInfo == null)
                {
                    vinInfo = GetVinInfo(repository, vin);
                }
                if (vinInfo == null) return subGroups;
                descriptionsMap.ForEach(q => q.Value.ForEach(x => x.isPart = false));
                var prCodes = vinInfo?.PRNumbers.Split(",").Distinct();
                List<PR> prInfoList = GetPRInfos(brandCode, prCodes, vinInfo);

                foreach (var subGroup in subGroups)
                {
                    // 有多个分组描述，合并相关的值，再处理。
                    var items = (descriptionsMap.Keys.Contains(subGroup.id) ? descriptionsMap[subGroup.id] : new List<SubGroupDescription>()).Where(d => d.uou == "U");

                    if (!items.Any())
                    {
                        subGroup.belongTo = true;
                        continue;
                    }

                    subGroup.belongTo = items.Any(item => CheckBelongToVin(brandCode, prInfoList, vinInfo, item, null));
                }

                // 将是否属于VIN的判断值带到分组的配件列表接口，避免再次处理，影响性能。
                //subGroups.ForEach(g => g.id = $"{g.id}_{(g.belongTo ? 1 : 0)}");

                return subGroups;
            }
        }

        protected List<Query_SubGroupResult> BuildEtkaSubGroup(List<Query_SubGroupResult> subGroups, Dictionary<string, List<SubGroupDescription>> descriptionsMap, string brandCode, List<PR> prInfoList, VinInfo vinInfo)
        {
            foreach (var subGroup in subGroups)
            {
                // 有多个分组描述，合并相关的值，再处理。
                var items = descriptionsMap.Keys.Contains(subGroup.id) ? descriptionsMap[subGroup.id] : new List<SubGroupDescription>();

                // 根据描述信息，更新名称等信息。
                SetSubGroupText(brandCode, subGroup, items);
            }
            // 处理通过VIN码过滤分组
            descriptionsMap.ForEach(q => q.Value.ForEach(x => x.isPart = false));

            foreach (var subGroup in subGroups)
            {

                // 有多个分组描述，合并相关的值，再处理。
                var items = (descriptionsMap.Keys.Contains(subGroup.id) ? descriptionsMap[subGroup.id] : new List<SubGroupDescription>()).Where(d => d.uou == "U");

                if (!items.Any())
                {
                    subGroup.belongTo = true;
                    continue;
                }

                subGroup.belongTo = items.Any(item => CheckBelongToVin(brandCode, prInfoList, vinInfo, item, null));
            }

            // 将是否属于VIN的判断值带到分组的配件列表接口，避免再次处理，影响性能。
            //subGroups.ForEach(g => g.id = $"{g.id}_{(g.belongTo ? 1 : 0)}");

            return subGroups;
        }

        protected bool CheckProductionDate(string productionDate, int? startYear, int? endYear)
        {
            if (string.IsNullOrEmpty(productionDate) || (!startYear.HasValue && !endYear.HasValue))
            {
                return true;
            }
            try
            {
                var vinYYMM = productionDate.Replace("-", "").Substring(0, 6).ToInt();
                if (startYear.HasValue)
                {
                    var itemYYMM = GetYYYYMM_For_MMYY(startYear.ToString());
                    if (itemYYMM > vinYYMM)
                    {
                        return false;
                    }
                }

                if (endYear.HasValue)
                {
                    var itemYYMM = GetYYYYMM_For_MMYY(endYear.ToString());
                    if (itemYYMM < vinYYMM)
                    {
                        return false;
                    }
                }
            }
            catch
            {
                return true;
            }
            return true;
        }

        protected bool CheckSalesType(string vkk, string salesType)
        {
            if (string.IsNullOrEmpty(vkk) || string.IsNullOrEmpty(vkk))
            {
                return true;
            }

            var salesTypes = vkk.Split(",", StringSplitOptions.RemoveEmptyEntries).Distinct();

            var minTypes = salesTypes.Where(q => q.Contains('-')).Select(q => q.Trim('-').Trim());
            var plusTypes = salesTypes.Where(q => !q.Contains('-')).Select(q => q.Trim('+').Trim());

            // 如果有 - 号销售类型码与当前VIN码相同，则为不匹配
            if (minTypes.Any(q => q.Equals(salesType))) return false;
            // 否则，如果没有任何销售类型能匹配，则为不匹配
            else if (plusTypes.Any() && !plusTypes.Any(q => q.Equals(salesType))) return false;
            return true;
        }

        protected bool CheckInteriorColour(string colors, string vinColor)
        {
            if (string.IsNullOrEmpty(colors) || string.IsNullOrEmpty(vinColor))
            {
                return true;
            }
            return colors.Contains(vinColor);
        }

        protected virtual List<Query_PartsResult> GetParts(DbRESTFulRepository repository, JObject param)
        {
            return repository.Invoke<Query_PartsResult>(CSIs.Query_Parts_For_ETKA, param);
        }

        protected List<PR> GetPRInfos(string brandCode, IEnumerable<string> prCodes, VinInfo vinInfo)
        {
            var allPRDic = ResourceHelper.EtkaPRsDict[brandCode];
            List<PR> prInfoList = new List<PR>();
            prCodes.ForEach(x =>
            {
                if (allPRDic.ContainsKey(x))
                {
                    prInfoList.AddRange(allPRDic[x].Where(p => MatchPR(p, vinInfo)));
                }
            });
            return prInfoList;
        }

        protected int GetYYYYMM_For_MMYY(string mmyy)
        {
            if (mmyy.Length == 3)
            {
                mmyy = "0" + mmyy;
            }
            mmyy = mmyy.Substring(2, 2) + mmyy.Substring(0, 2);
            return GetYYYYMM(mmyy);
        }

        protected int GetYYYYMM(string yymm)
        {
            int itemIntYYMM = yymm.ToInt();
            if (itemIntYYMM > 5000)
            {
                itemIntYYMM += 190000;
            }
            else
            {
                itemIntYYMM += 200000;
            }
            return itemIntYYMM;
        }

        protected const string reSubGroupCode = @"(?<subgroup>[\d]{3}-[\d]{3})";

        protected void BuildPartDisplay(DbRESTFulRepository repository, List<Query_PartsResult> result, string brandCode, List<SubGroupInfo> allGroups, string selectorId)
        {
            foreach (var item in result)
            {
                item.modelNotes = item.modelFilter;
                if (!string.IsNullOrEmpty(item.TSBem))
                {
                    item.notes = ResourceHelper.Textify(brandCode, item.TSBem.Split(Spliter, StringSplitOptions.RemoveEmptyEntries).Distinct());
                }

                if (!string.IsNullOrEmpty(item.description))
                {
                    string desc = item.description;
                    MatchCollection matchs = Regex.Matches(item.description, reSubGroupCode);
                    if (matchs.Count > 0)
                    {
                        foreach (Match match in matchs)
                        {
                            GroupCollection gc = match.Groups;
                            string subgroup = gc["subgroup"].Value;
                            string subgroupCode = subgroup.Replace("-", "");
                            if (allGroups == null)
                            {
                                allGroups = repository.Invoke<SubGroupInfo>(CSIs.Query_All_Group_By_SelectorId, new { selectorId });
                            }
                            var subGroupInfo = allGroups.Where(q => q.subGroupCode == subgroupCode).FirstOrDefault();
                            if (subGroupInfo != null)
                            {
                                desc = desc.Replace(subgroup, $"<a data='mainGroupId={subGroupInfo.mainGroupId},subGroupId={subGroupInfo.id}'>{subgroup}</a>");
                            }
                        }
                    }

                    item.notes += "\r\n" + desc;
                }
                if (!string.IsNullOrEmpty(item.notes))
                {
                    item.notes = item.notes.Trim().Trim(',');
                }
            }
            if (result.Any(x => !string.IsNullOrEmpty(x.partCode)) && result.Where(x => !string.IsNullOrEmpty(x.marketCode)).Select(q => q.marketCode).FirstOrDefault() == "SVW")
            {
                result[0].otherDescription = "注意:上汽大众各地销价不统一, 我站统一采取30%加价率";
            }
        }

        protected List<Query_PartsResult> MergeColorCodeParts(List<Query_PartsResult> result, VinInfo vinInfo)
        {
            Query_PartsResult currentPart = null;
            var currentPartFilterResult = false;
            var childCount = 0;
            foreach (var item in result)
            {
                if (string.IsNullOrEmpty(item.partCode)) continue;
                if (!string.IsNullOrEmpty(item.imageSN))
                {
                    if (currentPart != null && !currentPartFilterResult && childCount > 0)
                    {
                        currentPart.belongToVin = false;
                    }
                    currentPart = item;
                    currentPartFilterResult = false;
                    childCount = 0;
                    continue;
                }
                else
                {
                    if (currentPart == null) continue;
                    if (IsColorPart(item.partCode, currentPart.partCode))
                    {
                        childCount++;
                        if (currentPart.belongToVin)
                        {
                            //if (!string.IsNullOrEmpty(item.interiorColourType) && !string.IsNullOrEmpty(vinInfo.colorInterior))
                            //{
                            //    var colors = item.interiorColourType.Split(',').Where(x => !string.IsNullOrEmpty(x)).Select(x => x.Trim('-').Trim());
                            //    if (colors.All(x => x.Length == 2) && !colors.Any(x => x == vinInfo.colorInterior))
                            //    {
                            //        item.belongToVin = false;
                            //    }
                            //}
                            if (item.belongToVin)
                            {
                                currentPartFilterResult = true;
                            }
                        }
                        else
                        {
                            item.belongToVin = false;
                        }
                    }
                }
            }

            if (currentPart != null && !currentPartFilterResult && childCount > 0)
            {
                currentPart.belongToVin = false;
            }
            return result;
        }

        protected bool IsColorPart(string colorPartCode, string partCode)
        {
            if (colorPartCode.StartsWith(partCode) || colorPartCode.Substring(1).StartsWith(partCode) || colorPartCode.StartsWith(partCode.Substring(1)))
            {
                return true;
            }
            return false;
        }

        protected bool VinFilterForPart(string brandCode, List<PR> pRFamilies, VinInfo vinInfo, VinFilter filter, HashSet<string> vinComponents)
        {
            if (filter.uou != null && filter.uou.ToUpper() == "C") return false;

            return CheckBelongToVin(brandCode, pRFamilies, vinInfo, filter, vinComponents);
        }

        protected List<Query_PartsResult> MergeSubGroupDescriptionsToParts(DbRESTFulRepository repository, string brandCode, string selectorId,
            List<SubGroupDescription> groupDescriptions,
            List<Query_PartsResult> parts,
            Func<VinFilter, bool> vinFilterFunc)
        {
            var result = new List<Query_PartsResult>();

            if (groupDescriptions == null || groupDescriptions.Count == 0)
            {
                var skippFitler = vinFilterFunc == null;
                parts.ForEach(p => p.belongToVin = skippFitler || vinFilterFunc(p));
                result.AddRange(parts);
            }
            else
            {
                for (int i = 0; i < groupDescriptions.Count; i++)
                {
                    var desc = groupDescriptions[i];
                    var skippFitler = vinFilterFunc == null; // vinFilterFunc 为空时，表示不用筛选，所有belongToVin都为 true.
                    var groupBelongToVin = skippFitler || vinFilterFunc(desc);

                    var ids = $"{desc.TSBen ?? string.Empty},{desc.TSMoa ?? string.Empty}".Trim();
                    var item = new Query_PartsResult
                    {
                        partName = ResourceHelper.Textify(brandCode, ids.Split(Spliter, StringSplitOptions.RemoveEmptyEntries).Distinct()),
                        description = desc.description,
                        modelFilter = desc.modelFilter,
                        TSBem = desc.TSBem,
                        belongToVin = groupBelongToVin
                    };

                    result.Add(item);

                    if (groupDescriptions.Count == i + 1)
                    {
                        var groupParts = parts.Where(p => p.katalogId > desc.katalogId);
                        groupParts.ForEach(p => p.belongToVin = skippFitler || (!groupBelongToVin ? false : vinFilterFunc(p)));
                        result.AddRange(groupParts.OfType<Query_PartsResult>());
                    }
                    else
                    {
                        var nextGroupKatalogId = groupDescriptions[i + 1].katalogId;
                        var groupParts = parts.Where(p => p.katalogId > desc.katalogId && p.katalogId < nextGroupKatalogId);
                        groupParts.ForEach(p => p.belongToVin = skippFitler || (!groupBelongToVin ? false : vinFilterFunc(p)));
                        result.AddRange(groupParts.OfType<Query_PartsResult>());
                    }
                }
            }
            List<SubGroupInfo> allGroups = null;
            BuildPartDisplay(repository, result, brandCode, allGroups, selectorId);
            return result;
        }


        protected void MergeSubGroupDescriptionsToParts2(
            List<SubGroupDescription> groupDescriptions,
            List<PartCatalogResult> parts,
            Func<VinFilter, bool> vinFilterFunc)
        {
            var result = new List<Query_PartsResult>();

            if (groupDescriptions == null || groupDescriptions.Count == 0)
            {
                var skippFitler = vinFilterFunc == null;
                parts.ForEach(p => p.belongToVin = skippFitler || vinFilterFunc(p));
                result.AddRange(parts);
            }
            else
            {
                for (int i = 0; i < groupDescriptions.Count; i++)
                {
                    var desc = groupDescriptions[i];
                    var skippFitler = vinFilterFunc == null; // vinFilterFunc 为空时，表示不用筛选，所有belongToVin都为 true.
                    var groupBelongToVin = skippFitler || vinFilterFunc(desc);

                    if (groupDescriptions.Count == i + 1)
                    {
                        var groupParts = parts.Where(p => p.katalogId > desc.katalogId);
                        groupParts.ForEach(p => p.belongToVin = skippFitler || (!groupBelongToVin ? false : vinFilterFunc(p)));
                    }
                    else
                    {
                        var nextGroupKatalogId = groupDescriptions[i + 1].katalogId;
                        var groupParts = parts.Where(p => p.katalogId > desc.katalogId && p.katalogId < nextGroupKatalogId);
                        groupParts.ForEach(p => p.belongToVin = skippFitler || (!groupBelongToVin ? false : vinFilterFunc(p)));
                    }
                }
            }
        }

        protected virtual bool VerifyFeatureCodesWithNagitive(string brandCode, string prCodes, List<PR> vinPRs, VinInfo vinInfo, bool isImportant = false)
        {

            // 得到所有PR-Nummers （合并 featureCodes）
            var PRNummers = prCodes.Replace("/", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).Distinct();

            // RPNummer通过 - 号作排除逻辑。如：PR:351+,355-，即，351和非355都适用。
            var nummers = new HashSet<string>(PRNummers.Select(n => n.Trim('+', '-')));

            // 在当前Vin的PRs中过滤（包括Familie和Nummer）
            var vPRs = vinPRs.Where(p => nummers.Contains(p.PRNummer)).ToList();


            // 在所有PRs中过滤（包括Familie和Nummer）
            var aPRs = GetPRInfos(brandCode, nummers, vinInfo);

            aPRs = aPRs.GroupBy(q => q.PRNummer).Select(q => q.OrderByDescending(x => x.modelEpisTypes).ThenByDescending(x => x.startDate).ThenByDescending(x => x.endDate).ThenByDescending(x => x.MBA).First()).ToList();

            foreach (var items in aPRs.GroupBy(p => p.PRFamilie))
            {
                if (IgnoreFamilys.Contains(items.Key))
                {
                    continue;
                }
                var aPRNummers = items.Select(i => i.PRNummer);

                // Vin 中当前分组的 PRNummer
                var vPRNummers = vPRs.Where(p => p.PRFamilie == items.Key).Select(p => p.PRNummer);

                // 所有加号的 PRNummer，即只要有一个匹配就通过。
                var plusPRNummers = aPRNummers.Where(p => PRNummers.Contains(p) || PRNummers.Contains($"{p}+"));

                // 所有减号的 PRNummer，即都不匹配才能通过。
                var minusPRNummers = aPRNummers.Where(p => PRNummers.Contains($"{p}-"));

                if (plusPRNummers.Any(p => vPRNummers.Contains(p))) continue;
                else if (!vPRNummers.Any() && !minusPRNummers.Any() && plusPRNummers.Any())
                {
                    if (isImportant)
                    {
                        return false;
                    }
                    // pr 有 modelTpisTypes
                    if (items.Any(q => !string.IsNullOrEmpty(q.modelEpisTypes)))
                    {
                        return false;
                    }
                    //vin 有任意该族
                    if (vinPRs.Any(p => p.PRFamilie == items.Key))
                    {
                        return false;
                    }
                    continue;
                }
                else if (minusPRNummers.All(p => !vPRNummers.Contains(p))) continue;
                else return false;
            }

            return true;
        }

        protected string GetYYYYMMDD_For_breakPointDate(int value)
        {
            string ddmmyyyy = value.ToString();
            if (ddmmyyyy.Length < 7)
            {
                return "";
            }
            if (ddmmyyyy.Length == 7)
            {
                ddmmyyyy = "0" + ddmmyyyy;
            }
            return $"{ddmmyyyy.Substring(4, 4)}-{ddmmyyyy.Substring(2, 2)}-{ddmmyyyy.Substring(0, 2)}";
        }

        protected static readonly Dictionary<string, string> Dic_ReplaceType = new Dictionary<string, string> { { "F", "本部件由多个部件解释" }, { "L", "使用替代品" }, { "M", "本部件不再采用，也无替代品。作为替代方案，可以选用其它的一个或多个部件" }, { "O", "无替代品" } };
        protected static readonly Dictionary<string, string> Dic_WarehouseNote = new Dictionary<string, string> { { "A", "该零件与替换件兼容" }, { "F", "此零件已停止使用。如有疑问，请向仓储中心咨询" }, { "U", "该零件与替换件兼容，可重新仓储" } };


        protected const string reg1 = @"[\W]+D[ ]+(?<dateStr>[\d .\-*MJ]*>>[\d .\-*MJ]*)";
        protected const string reg3 = @"[\W]+F[ ]+(?<vinstr>[A-Z0-9 \-*]*>>[A-Z0-9 \-*]*)";
        protected const string reg4 = @"[\W]+M[ ]+(?<engineStr>[A-Z0-9 ]*>>[A-Z0-9 ]*)";
        protected virtual IEnumerable<dynamic> GetVinDetails(DbRESTFulRepository repository, Get_Model_InfoParam request)
        {
            return repository.Invoke(CSIs.Query_Vin_By_Code_For_ETKA, request);
        }
        protected bool CheckTimeStr(string timeFilter, VinInfo vinInfo)
        {
            if (string.IsNullOrEmpty(timeFilter)) return true;

            try
            {
                var matchedFilter = false;
                // ,D             >> - 30.07.2018

                MatchCollection matchCol1 = Regex.Matches(timeFilter, reg1);
                if (matchCol1.Count > 0)
                {
                    matchedFilter = true;
                    foreach (Match match in matchCol1)
                    {
                        GroupCollection gc = match.Groups;
                        string dateStr = gc["dateStr"].Value;
                        if (IsMathDateStr(dateStr, vinInfo.productionDate, vinInfo.modelYear))
                        {
                            return true;
                        }
                    }
                }

                // filter vin 段
                MatchCollection matchCol3 = Regex.Matches(timeFilter, reg3);
                if (matchCol3.Count > 0)
                {
                    matchedFilter = true;
                    foreach (Match match in matchCol3)
                    {
                        GroupCollection gc = match.Groups;
                        string vinStr = gc["vinstr"].Value;
                        if (IsMathVinStr(vinStr, vinInfo.code))
                        {
                            return true;
                        }
                    }
                }

                // filter engineCode and engineNumber
                MatchCollection matchCol4 = Regex.Matches(timeFilter, reg4);
                if (matchCol4.Count > 0)
                {
                    matchedFilter = true;
                    int engineNo = vinInfo.engineNumber.ToInt();
                    foreach (Match match in matchCol4)
                    {
                        GroupCollection gc = match.Groups;
                        string engineStr = gc["engineStr"].Value;
                        if (IsMathEngineStr(engineStr, vinInfo.engineCode, engineNo))
                        {
                            return true;
                        }
                    }
                }
                if (matchedFilter)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError(ex, $"Check Production Year failed. timeFilter: {timeFilter}, " +
                    $"modelYear: {vinInfo.modelYear}, modelYear: {vinInfo.productionDate}");
                return true;
            }
        }

        protected bool IsMathDateStr(string dateStr, string productionDate, string modelYear)
        {
            dateStr = dateStr.Replace("-", "").Trim();
            var fromTo = dateStr.Split(">>");
            var from = fromTo[0]?.Trim();
            var to = fromTo[1]?.Trim();
            int productionDateInt = 0;
            if (!string.IsNullOrEmpty(productionDate))
            {
                productionDate = productionDate.Replace("-", "").Trim();
                int.TryParse(productionDate, out productionDateInt);
            }

            var modelYearInt = modelYear.ToInt();
            if (!string.IsNullOrEmpty(from) && !FilterDateForSpecialStr(from, productionDateInt, modelYearInt, true))
            {
                return false;
            }
            if (!string.IsNullOrEmpty(to) && !FilterDateForSpecialStr(to, productionDateInt, modelYearInt, false))
            {
                return false;
            }
            return true;
        }

        protected bool FilterDateForSpecialStr(string dateStr, int productionDate, int modelYear, bool fromOrTo)
        {
            int date = 0;
            if (dateStr.Contains("MJ "))
            {
                //D             >> -    MJ 2016
                dateStr = dateStr.Replace("MJ", "").Trim();
                if (dateStr.Length == 4)
                {
                    if (int.TryParse(dateStr, out date))
                    {
                        if (!FilterDate(modelYear, date, fromOrTo))
                        {
                            return false;
                        }
                    }
                }
            }
            else if (dateStr.Contains('.'))
            {
                bool endWith = dateStr.EndsWith("*");
                //01.03.2010
                dateStr = dateStr.Replace(".", "").Replace("*", "");
                if (dateStr.Length == 8 && productionDate > 0)
                {
                    if (endWith)
                    {
                        dateStr = dateStr.Substring(4, 4) + dateStr.Substring(2, 2);
                        productionDate = productionDate / 100;
                    }
                    else
                    {
                        dateStr = dateStr.Substring(4, 4) + dateStr.Substring(2, 2) + dateStr.Substring(0, 2);
                    }
                    if (int.TryParse(dateStr, out date))
                    {
                        if (!FilterDate(productionDate, date, fromOrTo))
                        {
                            return false;
                        }
                    }
                }
            }
            else if (dateStr.Length == 6 && productionDate > 0)
            {
                //012006
                dateStr = dateStr.Substring(2, 4) + dateStr.Substring(0, 2);
                if (int.TryParse(dateStr, out date))
                {
                    productionDate = productionDate / 100;
                    if (!FilterDate(productionDate, date, fromOrTo))
                    {
                        return false;
                    }
                }
            }
            else if (dateStr.Length == 8 && productionDate > 0)
            {
                //20080922
                if (int.TryParse(dateStr, out date))
                {
                    if (!FilterDate(productionDate, date, fromOrTo))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        protected bool FilterDate(int productionDate, int date, bool fromOrTo)
        {
            if (fromOrTo)
            {
                if (productionDate < date)
                {
                    return false;
                }
            }
            else
            {
                if (productionDate > date)
                {
                    return false;
                }
            }
            return true;
        }

        protected bool IsMathEngineStr(string engineStr, string engineCode, int engineNo)
        {
            if (string.IsNullOrEmpty(engineStr) || string.IsNullOrEmpty(engineCode) || engineNo == 0)
            {
                return true;
            }
            var fromTo = engineStr.Split(">>");
            var fromStr = fromTo[0]?.Trim();
            var toStr = fromTo[1]?.Trim();
            engineCode = engineCode.Substring(0, 3);
            if (!string.IsNullOrEmpty(fromStr))
            {
                if (fromStr.Substring(0, 3) != engineCode)
                {
                    return false;
                }
                int fromNo = fromStr.Substring(fromStr.IndexOf(" ") + 1).Replace(" ", "").ToInt();
                if (engineNo < fromNo)
                {
                    return false;
                }

            }
            if (!string.IsNullOrEmpty(toStr))
            {
                if (toStr.Substring(0, 3) != engineCode)
                {
                    return false;
                }

                int toNo = toStr.Substring(toStr.IndexOf(" ") + 1).Replace(" ", "").ToInt();
                if (engineNo > toNo)
                {
                    return false;
                }
            }

            return true;
        }

        protected bool IsMathVinStr(string vinStr, string code)
        {
            if (string.IsNullOrEmpty(vinStr) || string.IsNullOrEmpty(code) || vinStr.Contains("*"))
            {
                return true;
            }
            var fromTo = vinStr.Split(">>");
            var fromStr = fromTo[0]?.Replace(" ", "").Trim();
            var toStr = fromTo[1]?.Replace(" ", "").Trim();

            if (!string.IsNullOrEmpty(fromStr) && !fromStr.Contains('*') && (!IsSameHead(fromStr, code, 2) || CompareVinStr(fromStr, code) > 0))
            {
                return false;
            }
            if (!string.IsNullOrEmpty(toStr) && !toStr.Contains('*') && (!IsSameHead(toStr, code, 2) || CompareVinStr(toStr, code) < 0))
            {
                return false;
            }
            return true;
        }

        protected bool IsSameHead(string str, string code, int len)
        {
            string str1 = str.Substring(0, len);
            string str2 = code.Substring(code.Length - str.Length, len);
            return str1 == str2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fromStr"></param>
        /// <param name="vinCode"></param>
        /// <returns>
        /// fromStr - vinCode
        /// </returns>
        protected int CompareVinStr(string fromStr, string vinCode)
        {
            var vinChars = vinCode.ToArray().TakeLast(fromStr.Length).ToList();
            for (int i = 0; i < fromStr.Length; i++)
            {
                char a = fromStr[i];
                if (a == '-')
                {
                    continue;
                }
                if (a < vinChars[i])
                {
                    return -1;
                }
                else if (a > vinChars[i])
                {
                    return 1;
                }
            }
            return 0;
        }

        protected void SetSubGroupText(string brandCode, Query_SubGroupResult subGroup, IEnumerable<SubGroupDescription> descriptions)
        {
            // subGroupName -> TSBen 去重后得到文本 + timeFilter
            var textIds = subGroup.subGroupDescCodes?.Split(Spliter, StringSplitOptions.RemoveEmptyEntries)?.Distinct();
            var timeFilter = string.Join(",", descriptions.Where(a => a.timeFilter != null).Select(a => a.timeFilter.Trim()));
            subGroup.name = ResourceHelper.Textify(brandCode, textIds, "$$", timeFilter)?.Replace("-", "").Replace("=", "").Trim(',');

            // description -> TSBem 去重后得到文本
            textIds = string.Join(",", descriptions.Select(a => a.TSBem))?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)?.Distinct();
            var descRemark = string.Join(",", descriptions.Where(d => d.description != null).Select(d => d.description)) ?? string.Empty;
            subGroup.remark = ResourceHelper.Textify(brandCode, textIds);
            if (!string.IsNullOrEmpty(descRemark)) subGroup.remark += "$$" + descRemark;
            if (!string.IsNullOrEmpty(subGroup.remark)) subGroup.remark = subGroup.remark.Trim().Trim(',');

            // modelling -> TSMoa 去重后得到文本 + modelFilter
            textIds = string.Join(",", descriptions.Select(a => a.TSMoa))?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)?.Distinct();
            var modelFilter = string.Join(",", string.Join(",", string.Join(",", descriptions.Where(a => a.modelFilter != null).Select(a => a.modelFilter.Trim()))?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)?.Distinct()));
            var modelling = ResourceHelper.Textify(brandCode, textIds);
            if (!string.IsNullOrEmpty(modelFilter)) modelling += "$$" + modelFilter;

            subGroup.applicableModel = modelling;
        }

        protected string GetSubGroupName(string brandCode, string subGroupDescCodes)
        {
            if (string.IsNullOrEmpty(subGroupDescCodes))
            {
                return string.Empty;
            }
            var textIds = subGroupDescCodes.Split(Spliter, StringSplitOptions.RemoveEmptyEntries)?.Distinct();
            return ResourceHelper.Textify(brandCode, textIds);
        }

        protected bool CheckCountrySymbol(string vinCountrySymbol, string countrySymbols)
        {
            if (string.IsNullOrEmpty(countrySymbols) || string.IsNullOrEmpty(vinCountrySymbol)) return true;

            var symbols = countrySymbols.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

            if (symbols.Count(i => !i.EndsWith('-')) > 0
                && !symbols.Where(i => !i.EndsWith('-')).Any(item => item.StartsWith(vinCountrySymbol)))
            {
                return false;
            }
            if (symbols.Count(i => i.EndsWith('-')) > 0
               && !symbols.Where(i => i.EndsWith('-')).All(item => !item.StartsWith(vinCountrySymbol)))
            {
                return false;
            }

            return true;
        }

        protected bool EqualsEngineCode(string engineCode1, string engineCode2, string brandCode)
        {
            if (string.IsNullOrEmpty(engineCode1) || string.IsNullOrEmpty(engineCode2) || engineCode1.Length < 3 || engineCode2.Length < 3)
            {
                return true;
            }
            if (engineCode1.Length == engineCode2.Length)
            {
                if (engineCode1.Equals(engineCode2, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
                else
                {
                    if (!ResourceHelper.EtkaEngineCodeDict[brandCode].Contains(engineCode1) || !ResourceHelper.EtkaEngineCodeDict[brandCode].Contains(engineCode2))
                    {
                        return engineCode1.Substring(0, 3).Equals(engineCode2.Substring(0, 3), StringComparison.OrdinalIgnoreCase);
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            else
            {
                return engineCode1.Substring(0, 3).Equals(engineCode2.Substring(0, 3), StringComparison.OrdinalIgnoreCase);
            }
        }

        protected virtual bool CheckEngineCode(string motorFilter, string engineCode, string brandCode)
        {
            if (!string.IsNullOrEmpty(motorFilter))
            {
                var engineCodes = motorFilter.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).Distinct().ToList();
                if (engineCodes.Count() > 0)
                {
                    // 如果有 - 号发动机代码与当前VIN码相同，则为不匹配
                    if (engineCodes.Any(c => c.Contains('-') && EqualsEngineCode(c.Trim('-'), engineCode, brandCode))) return false;
                    // 否则，如果没有任何发动机型号能匹配，则为不匹配
                    else if (!engineCodes.Any(c => EqualsEngineCode(c.Trim('+'), engineCode, brandCode))) return false;
                }
            }
            return true;
        }

        protected virtual bool CheckBelongToVin(string brandCode, List<PR> PRFamilies, VinInfo vinInfo, VinFilter vinFilter, HashSet<string> vinComponents)
        {
            if (!CheckProductionDate(vinInfo.productionDate, vinFilter.startYear, vinFilter.endYear)) return false;
            // 备注中的日期来判断是否属于VIN码
            if (!CheckTimeStr(vinFilter.timeFilter, vinInfo)) return false;
            // 国家代码是否属于Vin码
            if (!CheckCountrySymbol(vinInfo.countrySymbol, vinFilter.countrySymbol)) return false;

            if (!CheckInteriorColour(vinFilter.interiorColourType, vinInfo.colorInterior)) return false;

            if (!CheckColourType(vinFilter.colourType, vinInfo.colorTop, vinInfo.colorFinish)) return false;

            if (!CheckSalesType(vinFilter.vkk, vinInfo.salesType)) return false;

            if (!CheckEngineCode(vinFilter.motorFilter, vinInfo.engineCode, brandCode)) return false;

            if (!CheckComponent(vinFilter.component, vinComponents)) return false;

            return CheckOtherFilters(brandCode, PRFamilies, vinInfo, vinFilter);
        }

        protected virtual bool CheckColourType(string colourType, string colorTop, string colorFinish)
        {
            if (string.IsNullOrEmpty(colourType))
            {
                return true;
            }

            if (colourType.StartsWith("**"))
            {
                if (!string.IsNullOrEmpty(colorTop) && !colourType.Contains(colorTop))
                {
                    return false;
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(colorFinish) && !colourType.Contains(colorFinish))
                {
                    return false;
                }
            }
            return true;
        }

        protected bool CheckComponent(string component, HashSet<string> vinComponents)
        {
            if (string.IsNullOrEmpty(component) || vinComponents == null || vinComponents.Count == 0)
            {
                return true;
            }

            var temArry = component.Split("$$");
            foreach (var item in temArry)
            {
                if (vinComponents.Contains(item))
                {
                    return true;
                }
            }
            return false;
        }

        protected virtual bool CheckOtherFilters(string brandCode, List<PR> PRFamilies, VinInfo vinInfo, VinFilter vinFilter)
        {
            // 如果子组功能代码不匹配
            if (!VerifyFeatureCodes(brandCode, PRFamilies, vinInfo, vinFilter))
            {
                return false;
            }

            if (!string.IsNullOrEmpty(vinFilter.getriebes))
            {
                var codes = vinFilter.getriebes.Split(",").Select(q => q?.Trim('$'));
                if (!codes.Contains(vinInfo.transmissionCode))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 分组匹配功能代码，即：每一组至少要有一个是匹配的。
        /// </summary>
        /// <param name="vinPRs">VIN中，所有的PR信息。</param>
        /// <param name="prNummers"></param>
        /// <returns></returns>
        //protected virtual bool VerifyFeatureCodes(string brandCode, List<PR> vinPRs, VinInfo vinInfo, VinFilter vinFilter)
        //{
        //    if (!string.IsNullOrEmpty(vinFilter.featureCodes))
        //    {
        //        var prCodes = vinFilter.featureCodes.Replace("/", ",").Replace("-", "").Replace("+", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Distinct();
        //        if (!VerifyFeatureCodes(brandCode, vinPRs, vinInfo, prCodes, vinFilter.isPart))
        //        {
        //            return false;
        //        }
        //    }


        //    if (!string.IsNullOrEmpty(vinFilter.prFilter))
        //    {
        //        if(VerifyFeatureCodesWithNagitive(brandCode, vinFilter.prFilter, vinPRs, vinInfo, ))
        //        {
        //            return false;
        //        }
        //    }
        //    return true;
        //}
        protected virtual bool VerifyFeatureCodes(string brandCode, List<PR> vinPRs, VinInfo vinInfo, VinFilter vinFilter)
        {
            var prCodes = vinFilter.prFilter;

            // TODO: vinFilter.modelFilter?.Contains("PR") (猜测)
            if (!string.IsNullOrEmpty(vinFilter.featureCodes))
            {
                prCodes = vinFilter.prFilter + "," + vinFilter.featureCodes;
            }

            if (string.IsNullOrEmpty(prCodes))
            {
                return true;
            }

            return VerifyFeatureCodesWithNagitive(brandCode, prCodes, vinPRs, vinInfo, vinFilter.isPart);
        }

        protected virtual bool VerifyFeatureCodes(string brandCode, List<PR> vinPRs, VinInfo vinInfo, IEnumerable<string> prCodes, bool isImportant)
        {

            var vPRs = vinPRs.Where(p => prCodes.Contains(p.PRNummer) && MatchPR(p, vinInfo)).ToList();

            // 在所有PRs中过滤（包括Familie和Nummer）
            var aPRs = GetPRInfos(brandCode, prCodes, vinInfo);

            var exceptPrFamilys = aPRs.Select(q => q.PRFamilie).Distinct().Except(vPRs.Select(q => q.PRFamilie).Distinct());
            if (exceptPrFamilys.Count() == 0)
            {
                return true;
            }
            else
            {
                if (isImportant)
                {
                    return false;
                }
                else
                {
                    return !vinPRs.Any(q => exceptPrFamilys.Contains(q.PRFamilie));
                }
            }
        }

        protected bool MatchPR(PR p, VinInfo vinInfo)
        {
            var productionDate = vinInfo.productionDate.Replace("-", "").ToInt32();
            if (p.startDate > productionDate || p.endDate < productionDate) return false;
            if (!string.IsNullOrEmpty(p.modelEpisTypes) && !p.modelEpisTypes.Contains(vinInfo.episType)) return false;
            return true;
        }
        #endregion
        #region private method
        private dynamic GetSingleVin(IEnumerable<dynamic> vins)
        {
            if (vins == null || vins.Count() == 0) return null;
            vins = vins.Where(q => q.selectorId != null);
            if (vins.Count() == 1) return vins.First();
            foreach (var vin in vins)
            {
                int? productionDate = ((string)vin.ProductionDate)?.Replace("-", "").Substring(0, 6).ToInt();
                if (!productionDate.HasValue)
                {
                    return vin;
                }
                int? startDate = vin.modelYearStart;
                if (startDate.HasValue && startDate.Value > productionDate.Value)
                {
                    continue;
                }
                int? endDate = vin.modelYearEnd;
                if (endDate.HasValue && endDate.Value < productionDate.Value)
                {
                    continue;
                }
                return vin;
            }
            return vins.FirstOrDefault();
        }
        #endregion

        /// <summary>
        /// 快速找件
        /// </summary>
        /// <param name="repository"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public virtual object QuickQueryParts(DbRESTFulRepository repository, JObject param)
        {
            List<QuickPartResult> ret = new List<QuickPartResult>();
            string brandCode = param.Value<string>(ParamFields._brandCode);
            string partCodes = param.Value<string>(ParamFields._partCodes);
            string vin = param.Value<string>(ParamFields._vin);
            int selectorId = param.Value<int>(ParamFields._selectorId);
            if (!CheckQuickQueryPartsParam(brandCode, partCodes))
            {
                return ret;
            }

            IEnumerable<string> allPartCodes = GetSpcByPartCodes(repository, partCodes);
            if (allPartCodes != null && allPartCodes.Any())
            {
                ret = QuickQueryParts(repository, allPartCodes, brandCode);
                ret = QuickQueryPartsFilterByMarket(repository, ret, selectorId);
                ret = QuickQueryPartsFilterByUsage(repository, ret, vin, selectorId);
            }
            return ret;
        }
        List<QuickPartResult> QuickQueryPartsFilterByUsage(DbRESTFulRepository repository, List<QuickPartResult> parts, string vin, int selectorId)
        {
            List<QuickPartResult> ret = new List<QuickPartResult>();
            //车型下的快速找件
            if (string.IsNullOrEmpty(vin))
            {
                ret = QuickQueryPartsFilterByUsage_Model(repository, parts, selectorId);
            }
            //vin下快速找件
            else
            {
                ret = QuickQueryPartsFilterByUsage_Vin(repository, parts, selectorId, vin);
            }
            return ret;
        }
        protected virtual List<QuickPartResult> QuickQueryPartsFilterByUsage_Vin(DbRESTFulRepository repository, List<QuickPartResult> parts, int selectorId, string vin)
        {
            List<QuickPartResult> ret = new List<QuickPartResult>();
            //if (!string.IsNullOrEmpty(vin) && parts != null && parts.Any())
            //{
            //    List<string> partCodes = parts.Select(e => e.partCode).Distinct().ToList();
            //    List<Query_PartsResult> partUsages = repository.Invoke<Query_PartsResult>(CSIs.Query_Quick_PartUsage_For_Roewe, new { selectorId, partCodes });
            //    if (partUsages != null && partUsages.Any())
            //    {
            //        List<string> usedPartCodes = new List<string>();

            //        HashSet<string> setCodes = new HashSet<string>();
            //        dynamic vinInfo = repository.Invoke(CSIs.Query_Quick_Vin_For_Roewe, new { vin });
            //        if (vinInfo != null)
            //        {
            //            IEnumerable<dynamic> filters = repository.Invoke(CSIs.Get_Vin_Usage_Filter_For_Roewe, new { vinInfo.vinFeatureId, vinInfo.modelId });
            //            setCodes = new HashSet<string>(filters.Select(q => (string)q.ucCode));
            //        }

            //        foreach (var item in partUsages)
            //        {
            //            if (string.IsNullOrEmpty(item.featureCodes))
            //            {
            //                usedPartCodes.Add(item.partCode);
            //                continue;
            //            }
            //            var ucCodes = item.featureCodes.Split(',');

            //            var pass = false;
            //            foreach (var ucCode in ucCodes)
            //            {
            //                if (setCodes.Contains(ucCode))
            //                {
            //                    pass = true;
            //                    break;
            //                }
            //            }
            //            if (pass)
            //            {
            //                usedPartCodes.Add(item.partCode);
            //            }
            //        }

            //        if (usedPartCodes.Any())
            //        {
            //            ret = parts.Where(e => usedPartCodes.Contains(e.partCode)).ToList();
            //        }
            //    }
            //}
            return ret;
        }

        protected virtual List<QuickPartResult> QuickQueryPartsFilterByUsage_Model(DbRESTFulRepository repository, List<QuickPartResult> parts, int selectorId)
        {
            List<QuickPartResult> ret = new List<QuickPartResult>();
            if (selectorId > 0 && parts != null && parts.Any())
            {
                List<string> partCodes = parts.Select(e => e.partCode).Distinct().ToList();

                List<Query_PartsResult> partUsages = repository.Invoke<Query_PartsResult>(CSIs.Quick_PartUsage_By_SelectorId, new { selectorId, partCodes });
                if (partUsages != null && partUsages.Any())
                {
                    foreach (var item in parts)
                    {
                        if (partUsages.FirstOrDefault(e => e.partCode == item.partCode) != null)
                        {
                            ret.Add(item);
                        }
                    }
                }
            }
            return ret;
        }

        protected virtual List<QuickPartResult> QuickQueryPartsFilterByMarket(DbRESTFulRepository repository, List<QuickPartResult> parts, int selectorId)
        {
            List<QuickPartResult> ret = new List<QuickPartResult>();
            if (parts != null && parts.Any())
            {
                string marketCode = string.Empty;
                var modelInfo = repository.Invoke(CSIs.Get_MarketCode_By_SelectorId, new { selectorId });
                if (modelInfo != null)
                {
                    marketCode = modelInfo.priceMarketCode;
                }
                foreach (var item in parts)
                {
                    if (item.marketCode == marketCode)
                    {
                        ret.Add(item);
                    }
                }
            }
            return ret;
        }
        protected virtual List<QuickPartResult> QuickQueryParts(DbRESTFulRepository repository, IEnumerable<string> partCodes, string brandCode)
        {
            return repository.Invoke<QuickPartResult>(CSIs.Query_QuickParts, new { partCodes = partCodes, brandCode }); ;
        }
        bool CheckQuickQueryPartsParam(string brandCode, string partCodes)
        {
            bool res = true;
            if (string.IsNullOrEmpty(brandCode) || string.IsNullOrEmpty(partCodes))
            {
                return false;
            }
            var partCodeArr = partCodes.Split(new char[] { ',' });
            if (partCodeArr.Count() > 50)
            {
                return false;
            }

            return res;
        }

        protected virtual IEnumerable<string> GetSpcByPartCodes(DbRESTFulRepository repository, string partCodes)
        {
            List<string> result = new List<string>();
            if (!string.IsNullOrEmpty(partCodes))
            {
                var param = partCodes.Split(new char[] { ',' });
                result.AddRange(param);

                List<string> partList = repository.Invoke<string>(CSIs.Get_Spcs_By_PartCodes, new { partCodes = param });
                if (partList != null && partList.Any())
                {
                    partList.ForEach(e =>
                    {
                        if (!string.IsNullOrEmpty(e))
                        {
                            var tmpPartCodes = e.Split(new char[] { ',' });
                            result.AddRange(tmpPartCodes);
                        }
                    });
                }
            }
            return result.Distinct();
        }
    }
}



